// Copyright (C) 2010, Scott W. Dunlop <swdunlop@gmail.com>
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//    * Neither the name of the <organization> nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 
// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define NOSPEX_IMPL_SOURCE // Indicates that this is an implementation file.

#include "nospex.h"
#include "util.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <assert.h>

void nx_set_cairo_font( cairo_t* cr ){
    cairo_select_font_face( 
        cr, "Sans", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL 
    );
    cairo_set_font_size( cr, 10.0 );
}

void nx_cache_info( nx_info_p info, cairo_t* cr ){
    cairo_text_extents_t extents;
    cairo_text_extents( cr, info->data, &extents );
    
    double w = info->width = extents.width + 10.0;
    double h = info->height = extents.height + 10.0;
    
    cairo_surface_t* surf = cairo_image_surface_create( 
        CAIRO_FORMAT_ARGB32, w, h 
    );
    cr = cairo_create( surf );
    nx_set_cairo_font( cr );

    cairo_set_operator( cr, CAIRO_OPERATOR_CLEAR );
    cairo_paint( cr ); //TODO: Is this necessary?

    cairo_set_operator( cr, CAIRO_OPERATOR_OVER );

    cairo_rectangle( cr, 0, 0, w, h );
    cairo_set_source_rgb( cr, 0.2, 0.2, 0.2 );
    cairo_fill_preserve( cr );
    cairo_set_source_rgb( cr, 0.3, 0.3, 0.3 );
    cairo_stroke( cr );
    
    cairo_move_to( 
        cr, 
        5.0, h - 5.0  // Got NO clue why we have to invert y, here.
        // extents.x_bearing + 5.0, 
        // extents.y_bearing + 5.0
    ); 
    cairo_set_source_rgb( cr, 0.9, 0.9, 0.9 );
    cairo_show_text( cr, info->data );
    cairo_fill_preserve( cr );
    cairo_stroke( cr );
    
    cairo_destroy( cr );
    info->cache = surf;
}

void nx_draw_info( 
    nx_info_p info, cairo_t* cr, double x, double y, double max
){
    double w = info->width;
    double h = info->height;
    
    x = (double)(int)x; // Don't want a fractional offset, here..
    y = (double)(int)y;

    if( (x + w) > max ){ x = max - w; };
    if( (y + h) > max ){ y = max - h; }; 

    if( info->cache == NULL ){
        nx_cache_info( info, cr );
        // Workaround for a weird GCC bug.
        nx_draw_info( info, cr, x, y, max );
        return;
    };

    cairo_set_source_surface( cr, info->cache, x, y );
    cairo_rectangle( cr, x, y, w, h );
    cairo_fill( cr );
}

#define USE_RSRC_COLOR( rsrc ) \
    nx_resource_color( (nx_resource_p) rsrc, &red, &green, &blue ); \
    cairo_set_source_rgb( cr, red, green, blue ); 

int nx_draw_graph( nx_graph_p graph, cairo_t* cr ){
    nx_node_p node;
    nx_link_p link;
    nx_info_p info;
    nx_blip_p blip, next_blip;

    int x, y, x1, x2, y1, y2, radius;
    double red, green, blue, pitch;
    double fx, fy;
    
    graph->render_req = 0;
    graph->render_clock = nx_system_clock( ); // Used for debounces.
    
    radius = NX_NODE_RADIUS;
    pitch = graph->pitch;
    
    cairo_set_operator( cr, CAIRO_OPERATOR_SOURCE );
    USE_RSRC_COLOR( graph );
    cairo_paint( cr );
    cairo_rectangle( cr, 0.0, 0.0, pitch * 100.0, pitch * 100.0 );
    cairo_fill( cr );

    FOREACH_LINK( graph, link ){
        x1 = (int)( link->src->x * pitch );
        y1 = (int)( link->src->y * pitch );
        x2 = (int)( link->dst->x * pitch );
        y2 = (int)( link->dst->y * pitch );
        USE_RSRC_COLOR( link );
        cairo_move_to( cr, x1, y1 );
        cairo_line_to( cr, x2, y2 );
        cairo_stroke( cr );
    };
    
    nx_time now = nx_graph_clock( graph ); 
    blip = graph->first_blip; 
    
    while( blip != NULL ){
        next_blip = NEXT( blip );

        if( blip->expiry > now ){
            nx_calc_blip_position( blip, now, &fx, &fy );

            x = fx * pitch;
            y = fy * pitch;

            USE_RSRC_COLOR( blip );
            cairo_arc( cr, x, y, NX_BLIP_RADIUS, 0, M_PI * 2.0 );
            cairo_fill( cr );
        }else{
            nx_unlink_blip( blip );
        };

        blip = next_blip;
    };
    
    if( graph->first_blip == NULL ) graph->blips_req = NX_HALTED;

    FOREACH_NODE( graph, node ){
        x = (int)( node->x * pitch );
        y = (int)( node->y * pitch );
        
        double red, green, blue;
        nx_resource_color( (nx_resource_p) node, &red, &green, &blue );

        cairo_set_source_rgb( cr, red * 0.9, green * 0.9, blue * 0.9 );
        cairo_arc( cr, x, y, radius, 0, M_PI * 2.0 );
        cairo_fill( cr );

        cairo_set_source_rgb( cr, red * 1.1, green * 1.1, blue * 1.1 );
        cairo_arc( cr, x, y, radius, 0, M_PI * 2.0 );
        cairo_stroke( cr );
    };

    nx_set_cairo_font( cr );
    
    double max = (100.0 * pitch); // - 10.0;
    
    FOREACH_NODE( graph, node ){
        info = ((nx_element_p) node)->info;
        if( info == NULL ) continue;
        nx_draw_info( info, cr, node->x * pitch, node->y * pitch, max );
    };
    
    FOREACH_BLIP( graph, blip ){
        nx_info_p info = ((nx_element_p) blip)->info;
        if( info == NULL ) continue;
        nx_calc_blip_position( blip, now, &fx, &fy );

        x = fx * pitch;
        y = fy * pitch;

        nx_draw_info( info, cr, x, y, max );
    };
}

